; XEP-80 controller ROM disassembly
;
; CPU: NS405 terminal controller (8048 derivative)
; Clock speed: 12MHz
;
; External connections:
;   A13 enables the international character set within the character
;   set generator.
;
;   A14 disables the character set generator. In normal operation with
;   the ATASCII charsets, the display is set to use $0000-1FFF or
;   $2000-3FFF depending on whether the int'l charset is used. For
;   internal charset generator or pixel graphics operation,
;   $4000-5FFF is used instead to bypass the character ROM. A14
;   switching only affects display reads and not CPU reads/writes.
;
;   The character set generator itself contains inverse video
;   chars, except that $9B is conspicuously blank.
;
;   /INTR is tied to the printer BUSY line, thus indicating to the CPU
;   when a new byte may be sent.
;
;   RE12 (ROM extend A12) is tied to the printer STROBE line, indicating
;   to the printer when a byte is ready. Therefore, only 4K of external
;   program ROM may be used (only 2K is used currently anyway).
;
; Shared registers:
;   F0 = 1: Screen byte received
;   F1 = 1: Command byte received
;   T       0 for text mode, 3 for pixel graphics
;
; Register bank 0:
;   R1      Current row index (current Y + $20)
;   R2      Current X
;   R4      Last character
;   R5      Left margin
;   R6      Right margin
;   R7      Temporary; also extended byte for test commands
;
; Register bank 1:
;   R1/RB   A/HACC save
;   R2      Cursor update flags
;       This is actually coded as concatenated bit sequences:
;           1       Data byte
;           10      Horiz update
;           100     Vert update
;           101     Data byte + horiz update
;           1001    Data byte + vert update
;           10010   Horiz + vert update
;           100101  Data byte + Horiz + vert update
;   R3      Received byte
;   R4      Byte to transmit
;   R5      Interrupt status (during internal interrupt processing)
;   R6      Next row pointer to load
;   R7      Text horizontal scroll position (row addr low byte)
;
; Variables:
;   $14-15  Print buffer read pointer
;   $16-17  Print buffer write pointer
;   $20-38  Row 0-24 pointers (row high address byte)
;   $39     Previous cursor Y
;   $3A     Previous cursor X
;   $3B     List mode flag (sticky escape)
;   $3C     Escape flag
;   $3D     Temporary for screen insert/delete routines
;   $3E     Video control register (VCR) shadow
;       D0 = 1: Blink background (reverse video)
;       D1 = 1: Cursor blink enabled
;       D2 = 1: Cursor reverses video (instead of overwriting)
;       D3 = 1: Reverse video
;       D4 = 1: Enable external attribute memory
;       D5 = 1: Display enabled
;       D6,D7 = 0X: Text, internal chargen
;       D6,D7 = 10: Text, external chargen
;       D6,D7 = 11: Pixel graphics
;   $3F     Flags
;       D0 = 1: Burst is enabled
;       D1 = 1: Byte ready to send
;       D7 = 1: Printer output is enabled (coupled with burst)
;
; External memory map (text mode):
;   $0000-18FF  text screen
;   $1900-19FF  tab buffer
;   $1A00-1FFF  print buffer

;==========================================================================
; reset vector
;
L0000:
        JMP     L0009

;==========================================================================
        DB      000h
        
;==========================================================================
; external interrupt vector
;
L0003:
        JMP     L00B5

;==========================================================================
        DB      000h
        DB      000h

;==========================================================================
; internal interrupt vector
;
L0007:
        JMP     L0100

;==========================================================================
; reset (continued)
;
L0009:
        DIS     II
        DIS     XI
        CALL    L001F
        MOV     A,#01Fh
        MOV     UCR,A
        MOV     A,#090h
        
        ;set baud rate to 12MHz / 8 / (5 + 1) / 16 = 15625Hz
        MOV     PSR,A       ;set prescale factor to 8
        MOV     A,#005h
        MOV     BAUD,A      ;set baud rate divisor to 6
        MOV     A,#001h
        MOV     UMX,A       ;select /1 for send
        CALL    L0056
        CLR     F0
        EN      II
        JMP     L0242

;==========================================================================
L001F:
        MOV     A,#024h     ;fifoin out, intensity out, table lookup, SB8-15 output, 7 dots/cell, /1 clock
        MOV     SCR,A
        MOV     A,#086h
        MOV     VCR,A
        MOV     A,#019h
        MOV     HACC,A
        CLR     A
        MOV     ENDD,A      ;display end = $1900
        DEC     A
        MOV     AL0,A       ;attribute latch 0 = $ff
        MOV     AL1,A       ;attribute latch 1 = $ff
        CLR     A
        MOV     SROW,A      ;status row = 0
        MOV     HACC,A
        MOV     BEGD,A      ;display begin = 0
        MOV     HOME,A      ;home = 0
        MOV     CURS,A      ;cursor addr = $0000
        
        ;initialize timing params
        MOV     R0,#040h
        MOV     R3,#00Eh
L0037:
        CLR     A
        MOV     TCP,A
L0039:
        MOV     A,R0
        MOVP    A,@A
        MOV     @TCP,A
        INC     R0
        DJNZ    R3,L0039
        RET

        ;Timing control table (60Hz text)
        ;
        ; 105 chars/line * 7 dots/char = 735 dots/char
        ; 12MHz / 735 = 16326Hz
        ; 16326Hz / 272 = 60.02Hz
        ;
        DB      068h        ;TCP 0 - 105 char slots per line
        DB      04Fh        ;TCP 1 - 80 displayed chars per line
        DB      052h        ;TCP 2 - begin HSYNC at char 82
        DB      05Fh        ;TCP 3 - end HSYNC after char 95
        DB      091h        ;TCP 4 - chars are 10 scans tall, 2 extra scans/frame
        DB      01Ah        ;TCP 5 - 27 total rows (272 total scanlines)
        DB      018h        ;TCP 6 - 25 displayed rows
        DB      002h        ;TCP 7 - begin VSYNC on row 1 of first blank row, and end after row 3
        DB      017h        ;TCP 8 - status row is line 24
        DB      0F4h        ;TCP 9 - VBR/32 rate for blink, 50% duty cycle
        DB      030h        ;TCP 10 - Split block graphics 2L, 2M, 3R
        DB      036h        ;TCP 11 - Split block graphics 3T, 3M, 4B
        DB      089h        ;TCP 12 - Underline row 9
        DB      00Fh        ;TCP 13 - Cursor on rows 1-15

        ;Partial timing control table - 60Hz graphics
        ;
        ; 93 chars/line * 8 dots/char = 744 dots/char
        ; 12MHz / 744 = 16129.0Hz horizontal
        ; 16129.0Hz / 269 = 59.96Hz vertical
        ;
        DB      05Ch        ;TCP 0 - 93 char slots per line
        DB      027h        ;TCP 1 - 40 displayed chars per line
        DB      03Bh        ;TCP 2 - begin HSYNC at char 59
        DB      046h        ;TCP 3 - end HSYNC after char 71
        DB      098h        ;TCP 4 - chars are 10 scans tall, 9 extra scans/frame
        DB      019h        ;TCP 5 - 26 total rows (269 total scanlines)
        DB      013h        ;TCP 6 - 20 total rows
        DB      079h        ;TCP 7 - VSYNC on rows 7-9

;==========================================================================
; Soft reset state
;
L0056:
        MOV     A,#013h
        MOV     MASK,A
        MOV     A,#016h
        MOV     VINT,A
        MOV     R0,#03Fh    ;load flags addr
        CLR     A           ;
        MOV     @R0,A       ;clear flags 
        DEC     R0          ;load VCR shadow addr
        MOV     A,#0A6h     ;text (ext chars), disp on, XOR cursor, static cursor, blink chars
        MOV     @R0,A       ;set VCR shadow
        CLR     A           ;
        DEC     R0          ;
        DEC     R0          ;
        MOV     @R0,A       ;clear escape flag
        DEC     R0          ;
        MOV     @R0,A       ;clear list mode flag
        DEC     R0          ;
        DEC     A           ;
        MOV     @R0,A       ;prev cursor X = $FF
        DEC     R0          ;
        MOV     @R0,A       ;prev cursor Y = $FF
        MOV     R0,#017h    ;load print buffer write hi addr
        MOV     A,#0FAh     ;load >$FA00
        MOV     @R0,A       ;set print buffer write hi
        MOV     R0,#015h    ;load print buffer read hi addr
        MOV     @R0,A       ;set print buffer read hi
        INC     R0          ;load print buffer write lo
        CLR     A           ;load <$FA00
        MOV     @R0,A       ;set print buffer write lo
        MOV     R0,#014h    ;load print buffer read lo
        MOV     @R0,A       ;set print buffer read lo
        SEL     RB1
        MOV     R2,#000h
        MOV     R6,#037h
        MOV     R7,#000h
        SEL     RB0
        CLR     A
        MOV     T,A
        MOV     R4,A
        MOV     R2,A        ;X = 0
        MOV     R5,A        ;Left margin = 0
        MOV     R6,#04Fh    ;Right margin = 79
        MOV     R1,#01Fh    ;
        MOV     R3,#019h    ;25 rows to init
L0090:                      ;--- init row pointer array loop
        INC     R1
        MOV     @R1,A
        INC     A
        DJNZ    R3,L0090
        MOV     R1,#020h    ;Current row = 0
        MOV     R3,#09Bh    ;EOL clear byte
        CALL    L0643       ;Clear all RAM with EOL
        
        ;init tabs every 8 chars starting at 7
        MOV     A,#019h
        MOV     HACC,A
        MOV     A,#0FFh
        MOVL    R0,A
        MOV     R7,#001h
L00A3:
        CLR     A
        DJNZ    R7,L00A9
        MOV     R7,#008h
        INC     A
L00A9:
        MOVX    @R0,A
        DJNZ    R0,L00A3
        MOVX    @R0,A
        
        ;set tab at char 2
        MOV     R0,#002h
        INC     A
        MOVX    @R0,A
        
        MOV     A,#0A6h
        MOV     VCR,A
        RET

;==========================================================================
; external interrupt handler (printer ready)
;
L00B5:
        SEL     RB1         ;switch to interrupt context
        MOV     R0,A        ;save A
        MOV     A,HACC      ;save HACC
        MOV     R5,A        ;(cont.)
        MOV     R1,#015h    ;load print buffer read ptr hi addr
        MOV     A,@R1       ;load print buffer read ptr hi
        MOV     HACC,A      ;-> hacc
        DEC     R1          ;go to lo addr
        MOV     A,@R1       ;load print buffer read ptr lo
        INC     A           ;next byte
        JNZ     L00C9       ;jump if no page crossing
        MOV     A,HACC      ;load read ptr hi
        JNZ     L00C7       ;jump if no buffer wrap
        MOV     A,#0FAh     ;wrap to bottom of buffer
L00C7:
        MOV     HACC,A      ;update pointer hi
        CLR     A           ;update pointer lo
L00C9:
        MOVL    R1,A
        MOVX    A,@R1       ;read byte from print buffer
        OUT     PORT        ;output to printer port
        SEL     MB2         ;assert printer STROBE line
        JMP     L00CF       ;delay a short bit
L00CF:
        SEL     MB0         ;deassert printer STROBE line
        JMP     L00D2

L00D2:
        MOVL    A,R1
        MOV     R1,#014h    ;load print buffer read ptr lo addr
        MOV     @R1,A       ;store print buffer read ptr lo
        MOV     R1,#016h    ;load print buffer write ptr lo addr
        CLR     C           ;clear carry
        XRL     A,@R1       ;check if <readptr == <writeptr
        JNZ     L00DD       ;skip if not
        CPL     C           ;set carry
L00DD:
        MOV     A,HACC      ;load print buffer read ptr hi
        MOV     R1,#015h    ;load print buffer read ptr hi addr
        MOV     @R1,A       ;store it
        MOV     R1,#017h    ;load print buffer write ptr hi addr
        XRL     A,@R1       ;check if >readptr == >writeptr
        JNZ     L00EB       ;jump if hi bytes different (buffer not empty)
        JNC     L00EB       ;jump if lo bytes different
        DIS     XI          ;print buffer empty - disable printer intr
        JMP     L00F4       ;time to go

L00EB:
        MOV     R1,#03Fh    ;load flags var addr
        MOV     A,@R1       ;load flags
        RLC     A           ;bit 7 -> carry
        JNC     L00F4       ;skip if not in printer mode
        MOV     A,#01Fh     ;xmit on, no loopback, no break, mark parity, 2 stop bits, 8 data bits
        MOV     UCR,A       ;
L00F4:
        MOV     A,R5        ;restore HACC
        MOV     HACC,A      ;(cont.)
        MOV     A,R0        ;restore A
        RETR                ;all done

        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        
;==========================================================================
; internal interrupt handler
;
L0100:
        SEL     RB1         ;1  switch to interrupt context
        MOVL    R1,A        ;1  save HACC/A
        MOV     A,INTR      ;1  read and clear interrupt status
        MOV     R5,A        ;1  save it
        MOV     A,T         ;1  load text/graphics flag
        JNZ     L010C       ;2  jump if graphics mode
        MOV     A,R5        ;1  restore interrupt status
        JB0     L0114       ;2  (+4) vertical interrupt
L010A:
        JB1     L0118       ;2  (+15) end of row interrupt
L010C:
        MOV     A,R5        ;1  restore interrupt status
        JB3     L0128       ;2  (+45) transmit done
L010F:
        MOV     A,R5        ;1  restore interrupt status
        JB4     L0176       ;2  receive full
L0112:
        MOVL    A,R1        ;1  restore HACC/A
        RETR

;==========================================================================
; vertical interrupt (4 cycles)
;
L0114:
        MOV     R6,#037h    ;2
        JMP     L010A       ;2

;==========================================================================
; end of row interrupt (15 cycles)
;
L0118:
        MOV     A,R6        ;1  get row index
        MOV     R0,A        ;1  -> addr
        MOV     A,@R0       ;1  load row pointer
        MOV     HACC,A      ;1  -> address hi
        MOV     A,R7        ;1  load scroll offset -> address lo
        MOV     HOME,A      ;1  set next row address
        INC     R6          ;1  next row
        MOV     A,R6        ;1  get row index
        XRL     A,#038h     ;2  check if we're on row 24
        JNZ     L010C       ;2  exit if not
        MOV     R6,#020h    ;1  wrap to row 0
        JMP     L010C       ;2  exit

;==========================================================================
; transmit done interrupt (45 cycles)
;
L0128:
        MOV     R0,#03Fh    ;2  load flags addr
        MOV     A,@R0       ;1  read flags
        RRC     A           ;1  test if we have a byte to send
        RRC     A           ;1  (cont.)
        JNC     L010F       ;2  exit if not set (-> 7 total)
        MOV     A,R2        ;1  load cursor update flags
        JZ      L0143       ;2  skip if no cursor updates needed
        CLR     C           ;1
        RRC     A           ;1  check data bit flag
        JC      L0150       ;2  jump if data byte required
        MOV     HACC,A      ;1  save update mask
        MOV     A,#017h     ;2  xmit on, no loopback, normal output, space parity, two stop bits, 8 data bits
        MOV     UCR,A       ;1  set UART control register
        MOV     A,HACC      ;1  load update mask
        RRC     A           ;1  check if X update bit set
        JC      L0158       ;2  do X update if so
        RRC     A           ;1  check if Y update bit set
        JC      L016E       ;2  do Y update if so
        MOV     R2,#000h    ;1  clear update flags
L0143:
        MOV     A,#01Fh     ;2  xmit on, no loopback, normal output, mark parity, two stop bits, 8 data bits
        MOV     UCR,A       ;1  set UART control register
        MOV     A,T         ;1  load text/graphics flag
        XRL     A,#013h     ;2  or in other interrupts we want
        MOV     MASK,A      ;1  set interrupt mask
        MOV     A,@R0       ;1  load flags
        ANL     A,#0FDh     ;2  turn off byte-to-send bit
        MOV     @R0,A       ;1  save flags
        JMP     L010F       ;1  all done [37 cycles total]

L0150:                      ;[-> 13 cycles]
        MOV     R2,A        ;1
        MOV     A,#01Fh     ;2  xmit on, no loopback, normal output, mark parity, two stop bits, 8 data bits
        MOV     UCR,A       ;1  set UART control reg
        MOV     A,R4        ;1  retrieve byte to send
L0155:                      ;[-> 42 cycles]
        OUT     XMTR        ;1  send byte
        JMP     L010F       ;2  end interrupt handling

L0158:                      ;[-> 21 cycles]
        MOV     R2,A        ;1
        JZ      L015D       ;2
        MOV     A,#080h     ;2  cursor Y follows
L015D:
        MOV     R0,A        ;1  save 'cursor Y follows' flag
        SEL     RB0         ;1  switch to main reg bank
        MOV     A,R2        ;1  load current X
        CLR     C           ;1
        ADD     A,#0B0h     ;2  check if cursor X >= $50
        JNC     L0169       ;2  skip if < $50
        MOV     A,#050h     ;2  cursor X >= $50, no cursor follows
        JMP     L016A       ;2  send and exit

L0169:                      ;[-> 34 cycles]
        MOV     A,R2        ;1  load current X
L016A:                      ;[-> 38 cycles]
        SEL     RB1         ;1  switch back to int reg bank
        ORL     A,R0        ;1  merge in 'cursor Y follows' flag
        JMP     L0155       ;2  send and exit

L016E:                      ;[-> 24 cycles] --- send vert cursor update
        MOV     R2,A        ;1
        SEL     RB0         ;1  switch to main reg bank
        MOV     A,R1        ;1  load current Y
        SEL     RB1         ;1  switch back to int reg bank
        ORL     A,#0C0h     ;2  or in 'cursor Y' indicator bits
        JMP     L0155       ;2  send and exit

;==========================================================================
; receive full interrupt handler
;
; Note that it is up to 102 cycles from interrupt to when we can first set
; the busy flag.
;
L0176:
        IN      RCVR        ;1  receive byte
        MOV     HACC,A      ;1  stash character
        MOV     A,STAT      ;1  read status
        JB2     L01CC       ;2  exit if framing error
        JB3     L01CE       ;2  exit if hardware receive overrun
        JF0     L01D0       ;2  exit if software receive overrun
        CLR     F1          ;1  set command flag
        CPL     F1          ;1  (cont.)
        JB1     L01C7       ;2  jump if parity error (D8=1 -> command)
        CLR     F1          ;1  clear command flag
        MOV     R0,#03Fh    ;1  load flags addr
        MOV     A,@R0       ;1  load flags
        RRC     A           ;1  check burst bit (bit 0)
        JNC     L01C7       ;2  skip if not in burst mode
        MOV     A,R0        ;1  A <- $3F
        MOV     UCR,A       ;1  [->20 cycles] set break mode (indicate busy)
        MOV     A,@R0       ;load flags
        RLC     A           ;check printer bit (bit 7)
        JNC     L01C7       ;jump if not in printer mode
        MOV     A,HACC      ;retrieve received byte
        MOV     R3,A        ;stash in R3
        MOVL    A,R0        ;RA -> HACC
        MOV     A,HACC      ;-> A
        MOV     R5,A
        MOV     R0,#017h
        MOV     A,@R0
        MOV     HACC,A
        DEC     R0
        MOV     A,@R0
        INC     A               ;next page
        JNZ     L01A5
        MOV     A,HACC
        JNZ     L01A3
        MOV     A,#0FAh
L01A3:
        MOV     HACC,A      ;reset print addr to $FA00
        CLR     A           ;(cont.)
L01A5:
        MOVL    R0,A
        MOV     A,R3        ;retrieve received byte
        MOVX    @R0,A       ;store in printer buffer
        EN      XI          ;enable printer interrupt
        MOVL    A,R0
        MOV     R0,#016h    ;load print buffer write ptr lo addr
        MOV     @R0,A       ;write print buffer write ptr lo
        MOV     R0,#014h    ;load print buffer read ptr lo addr
        CLR     C           ;clear carry (lo bytes different)
        XRL     A,@R0       ;compare read lo with write lo
        JNZ     L01B4       ;jump if not equal
        CPL     C           ;mark lo bytes same
L01B4:
        MOV     A,HACC      ;load write ptr high byte
        MOV     R0,#017h    ;load write ptr high byte addr
        MOV     @R0,A       ;save write ptr high byte
        MOV     R0,#015h    ;load read ptr high byte addr
        XRL     A,@R0       ;compare ptr high bytes
        JNZ     L01BF       ;jump if hi bytes different
        JC      L01C2       ;jump if lo bytes same
L01BF:
        MOV     A,#01Fh     ;set mark parity and end break mode (no longer busy)
        MOV     UCR,A       ;set UART control register
L01C2:
        MOV     A,R5
        MOV     HACC,A
        MOVL    R0,A
        JMP     L0112

L01C7:                      ;--- screen byte received
        CPL     F0          ;signal main thread that a byte is available
        MOV     A,HACC      ;retrieve received byte
        MOV     R3,A        ;store byte
        JMP     L0112       ;all done

L01CC:
        JMP     L0112

L01CE:
        JMP     L0112

L01D0:
        JMP     L0112

;==========================================================================
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh

;==========================================================================
; Main loop
;
L0200:
        ; send command status
        ;
        ;   F1 = 0  No data byte precedes cursor data
        ;   F1 = 1  Data byte precedes cursor data
        ;
        JF1     L020D       ;skip burst mode check if we have a data byte
        MOV     R0,#03Fh    ;load flags addr
        MOV     A,@R0       ;load flags
        RRC     A           ;get burst bit
        JNC     L020D       ;skip if not in burst mode
        MOV     A,#01Fh     ;mark parity
        MOV     UCR,A       ;write UART control
        JMP     L0241       ;wait for next byte

L020D:
        CLR     C
        CLR     A
        MOV     HACC,A      ;clear update flags
        MOV     R0,#039h    ;load last Y addr
        MOV     A,R1        ;load current Y
        XRL     A,@R0       ;check if Y has changed
        JZ      L021C       ;jump if not
        MOV     A,R1        ;load current Y
        MOV     @R0,A       ;update last Y
        CPL     C           ;set carry to indicate vert update needed
        MOV     A,#004h     ;set update flags to 100 (vert update needed)
        MOV     HACC,A      ;...
L021C:
        MOV     R0,#03Ah    ;load last X
        MOV     A,R2        ;load current X
        XRL     A,@R0       ;check if X has changed
        JNZ     L0226       ;skip if so
        JC      L022E       ;jump if Y has changed -> send vert only
        JNC     L0228       ;jump if Y hasn't changed -> send horiz anyway

L0226:                      ;--- X has changed
        MOV     A,R2        ;load current X
        MOV     @R0,A       ;update last X
L0228:
        CLR     C           ;set carry
        CPL     C           ;(cont.)
        MOV     A,HACC      ;retrieve update flags
        RLC     A           ;add 10 to update flags (send horiz update)
        RL      A           ;...
        MOV     HACC,A      ;store update flags
L022E:
        MOV     A,HACC      ;retrieve update flags
        JNF1    L0234       ;skip if no data byte
L0231:
        CLR     C           ;set carry
        CPL     C           ;(cont.)
        RLC     A           ;shl, set bit 0 (data byte present)
L0234:
        SEL     RB1
        MOV     R2,A        ;set cursor update flags for interrupt handler
        SEL     RB0
        MOV     R0,#03Fh    ;load flags addr
        MOV     A,@R0       ;load flags
        ORL     A,#002h     ;set transmit flag
        MOV     @R0,A       ;save flags
        MOV     A,T         ;load text/graphics flag
        XRL     A,#01Bh     ;or in other interrupts we want on
        MOV     MASK,A      ;re-enable interrupts
L0241:
        CLR     F0          ;signal that we are ready to receive
L0242:                      ;--- service loop
        JNF0    L0242       ;spin until byte received
        SEL     RB1         ;switch to interrupt context
        MOV     A,R3        ;read character from context 1
        SEL     RB0         ;switch to main context
        MOV     R3,A        ;stash received byte
        JF1     L028D       ;jump if command
        MOV     R4,A        ;set last character value
        MOV     A,T
        JNZ     L0296       ;jump if in pixel graphics mode
        MOV     A,R3        ;read character
        XRL     A,#09Bh     ;check for EOL
        JNZ     L025F       ;jump if not EOL
        MOV     A,R6        ;load right margin
        MOV     R2,A        ;move cursor to right margin
        CLR     A           ;set overwritten char to 0
        MOV     HACC,A      ;(cont.)
        CALL    L05C7       ;advance a character
        JMP     L0200       ;all done

L025B:
        CALL    L05C3       ;write character and advance
        JMP     L0200       ;all done

L025F:
        MOV     R0,#03Bh    ;select list mode flag
        MOV     A,@R0       ;read list mode flag
        INC     R0          ;advance to escape flag
        ORL     A,@R0       ;or with escape flag
        JNZ     L025B       ;jump if escaping is active
        MOV     A,R3        ;get current character
        XRL     A,#01Bh     ;check for ESC
        JNZ     L0270       ;jump if not
        INC     A           ;A=1
        MOV     @R0,A       ;set escape flag
        MOV     A,R3
        JMP     L0200

L0270:
        MOV     A,R1        ;get current Y
        XRL     A,#038h     ;check if on status row (row 24)
        JNZ     L027E       ;jump if not
        MOV     A,R3        ;get current character
        XRL     A,#09Ch     ;check if clear
        JNZ     L025B       ;jump if not
        CALL    L07DF       ;do clear
        JMP     L0200       ;all done

L027E:
        MOV     A,R3        ;get current character
        MOV     R0,#01Ch    ;check if bits 2-4 set
        ANL     A,R0        ;
        XRL     A,R0        ;
        JNZ     L025B       ;jump if not a control character
        MOV     A,R3        ;get current charcater
        XRL     A,R0        ;clear bits 2-4
        RR      A           ;shift down
        SWAP    A           ;swap nibbles
        CALL    L0500       ;do control char dispatch
        JMP     L0200

L028D:
        CALL    L02FB       ;dispatch command
        JF1     L0241       ;jump if no output
        JZ      L0231       ;jump if return byte only
        CPL     F1          ;clear command flag to enable cursor sending
        JMP     L0200       ;set up cursor send

L0296:
        CALL    L02D7       ;reverse bit order
        MOV     @CURS,A     ;write byte
        INC     CURS        ;advance to next location
        JMP     L0200

;==========================================================================
; $90-9F command dispatch (cont.)
;
L029C:
        MOV     A,R3        ;load command byte
        ANL     A,#008h     ;check if $98-9F
        JNZ     L02A3       ;jump to dispatch if so
L02A1:
        JMP     L032D       ;jump to set vertical pos

L02A3:
        MOV     A,R3        ;load command byte
        ANL     A,#007h
        ADD     A,#0A9h
        JMPP    @A

        DB      0A1h        ;$98 - Set cursor to status row
        DB      0B1h        ;$99 - Set graphics to 60Hz
        DB      0CDh        ;$9A - Modify graphics to 50Hz
        DB      0EFh        ;$9B - Reserved (NOP)
        DB      0EFh        ;$9C - Reserved (NOP)
        DB      0EFh        ;$9D - Reserved (NOP)
        DB      0EFh        ;$9E - Reserved (NOP)
        DB      0EFh        ;$9F - Reserved (NOP)

;==========================================================================
; Set graphics to 60Hz command ($99)
;
L02B1:
        MOV     A,#010h     ;enable only receive interrupt
        MOV     MASK,A
        MOV     A,#006h     ;ext=fifo in clock, ext=intensity, sequential addressing, SB8-15 output, 8 dots/cell, cpu = dot clock
        MOV     SCR,A       ;set scan control register
        MOV     A,#003h
        MOV     T,A
        MOV     A,#0E6h     ;pixel gfx, disp on, int attr, white on black, cursor reverses, static cursor, blinking char
        MOV     VCR,A
        MOV     A,#040h
        MOV     HACC,A
        CLR     A
        MOV     BEGD,A      ;display begin = $4000
        MOV     HOME,A      ;display start = $4000
        MOV     CURS,A      ;cursor = $4000
        DEC     A
        MOV     HACC,A
        MOV     ENDD,A      ;display end = $FFFF
        MOV     R0,#04Eh
        MOV     R3,#008h
        JMP     L0037

;==========================================================================
; Modify graphics to 50Hz command ($9A)
;
; This changes timing params to:
;
;   26 rows * 12 lines + 11 lines = 323 scanlines
;   16129.0Hz / 269 = 49.93Hz vertical
;
L02CD:
        MOV     R3,#0BAh    ;12 line chars, 11 extra scanlines per frame
        CALL    L03A8       ;set TCP 4
        MOV     R3,#010h    ;16 vertical blank lines
        MOV     R0,#006h    ;select TCP 6 (vertical blank register)
        JMP     L03AA       ;set TCP 6 and exit

;==========================================================================
; reverse bit order
;
; input:
;   R3 = byte to reverse (preserved)
;
; output:
;   A = reversed byte
;
L02D7:
        MOV     A,R3        ;76543210
        RR      A           ;07654321
        ANL     A,#088h     ;0---4---
        MOV     R0,A
        MOV     A,R3        ;76543210
        RL      A           ;65432107
        ANL     A,#011h     ;---3---7
        ORL     A,R0        ;0--34--7
        MOV     R0,A
        MOV     A,R3        ;76543210
        SWAP    A           ;32107654
        MOV     R3,A
        RR      A           ;43210765
        ANL     A,#022h     ;--2---6-
        ORL     A,R0        ;0-234-67
        MOV     R0,A
        MOV     A,R3        ;32107654
        RL      A           ;21076543
        ANL     A,#044h     ;-1---5--
        ORL     A,R0        ;01234567
L02EF:
        RET
        
;==========================================================================
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh

;==========================================================================
; Dispatch command
;
L02FB:
        SWAP    A
        ANL     A,#00Fh
        ADD     A,#001h
        JMPP    @A

        DB      011h        ;$0x - set horizontal pos
        DB      011h        ;$1x - set horizontal pos
        DB      011h        ;$2x - set horizontal pos
        DB      011h        ;$3x - set horizontal pos
        DB      011h        ;$4x - set horizontal pos
        DB      018h        ;$5x - set horizontal pos high nibble
        DB      020h        ;$6x - set left margin low nibble
        DB      025h        ;$7x - set left margin high nibble
        DB      02Dh        ;$8x - set vertical cursor position 0-15
        DB      0FBh        ;$9x - multiplexed
        DB      03Ah        ;$Ax - set right margin low nibble
        DB      03Fh        ;$Bx - set right margin high nibble
        DB      047h        ;$Cx - multiplexed
        DB      060h        ;$Dx - multiplexed
        DB      0FFh        ;$Ex - multiplexed
        DB      0FFh        ;$Fx - multiplexed
        
;==========================================================================
; Set horizontal position command ($00-4F)
;
L0311:
        MOV     A,R3        ;get command byte
L0312:
        MOV     R2,A
        MOV     R0,#03Ah
L0315:
        MOV     @R0,A
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; Set horizontal position high nibble ($50-5F)
;
L0318:
        MOV     A,R3
        SWAP    A
        XRL     A,R2
        ANL     A,#0F0h
        XRL     A,R2
        JMP     L0312

;==========================================================================
; Set left margin low nibble ($60-6F)
;
L0320:
        MOV     A,R3
        ANL     A,#00Fh
        MOV     R5,A
L0324:
        RET

;==========================================================================
; Set left margin high nibble ($70-7F)
;
L0325:
        MOV     A,R3
        SWAP    A
        XRL     A,R5
        ANL     A,#0F0h
        XRL     A,R5
        MOV     R5,A
        RET

;==========================================================================
; Set vertical cursor position 0-15 ($80-8F)
; Set vertical cursor position 16-23 ($90-97)
; Set cursor to status row 24 ($98)
;
L032D:
        MOV     A,R3        ;load command byte
        CLR     C
        ADD     A,#067h     ;test if > $98
        JC      L0324       ;ignore and exit if so
        ADD     A,#039h     ;command + $A0 = Y + $20
        MOV     R1,A        ;set as cursor row index
        MOV     R0,#039h    ;select last Y
        JMP     L0315       ;set that too and recomp+exit

;==========================================================================
; Set right margin low nibble ($A0-AF)
;
L033A:
        MOV     A,R3
        XRL     A,#0E0h
        MOV     R6,A
        RET

;==========================================================================
; Set right margin high nibble ($B0-BF)
;
L033F:
        MOV     A,R3
        SWAP    A
        XRL     A,R6
        ANL     A,#0F0h
        XRL     A,R6
        MOV     R6,A
        RET

;==========================================================================
; $C0-CF command dispatch
;
L0347:
        MOV     A,R3        ;get command
        ANL     A,#00Fh     ;mask to low nibble
        JNZ     L0358       ;skip if not $C0 (get char) command
        MOV     A,@CURS     ;read char at cursor
        SEL     RB1         ;swap to interrupt bank
        MOV     R4,A        ;set as byte to transmit
        SEL     RB0         ;swap back to main bank
        CLR     A           ;
        MOV     HACC,A      ;
        CALL    L05CB       ;advance to next char pos
        CLR     A           ;return $01
        INC     A           ;(cont.)
        CLR     F1
        RET

L0358:
        CALL    L0600
        SEL     RB1         ;switch to intr bank
        MOV     R4,A        ;set byte to transmit
        SEL     RB0         ;switch to main bank
        CLR     A
        CLR     F1
        RET

;==========================================================================
; $D0-DF command dispatch
;
L0360:
        MOV     A,R3
        ANL     A,#00Fh
        ADD     A,#066h
        JMPP    @A

        DB      076h        ;$D0 - clear list flag
        DB      076h        ;$D1 - set list flag
        DB      0E1h        ;$D2 - set screen normal mode
        DB      0E1h        ;$D3 - set screen burst mode
        DB      07Ch        ;$D4 - select character set A
        DB      07Ch        ;$D5 - select character set B
        DB      09Bh        ;$D6 - select XEP80 internal charset
        DB      0A6h        ;$D7 - modify text to 50Hz
        DB      0AFh        ;$D8 - cursor off
        DB      0B5h        ;$D9 - cursor on continuous
        DB      0B5h        ;$DA - cursor on blink
        DB      0C5h        ;$DB - move cursor to start of logical line
        DB      0DCh        ;$DC - set scroll window to cursor X value
        DB      0E7h        ;$DD - set printer output
        DB      0EDh        ;$DE - select white on black
        DB      0EDh        ;$DF - select black on white

;==========================================================================
; clear list flag ($D0)
; set list flag ($D0)
;
L0376:
        CALL    L04E6       ;mask to low bit of command
        MOV     R0,#03Bh    ;load list flag addr
        MOV     @R0,A       ;set list flag
        RET

;==========================================================================
; set character set A ($D4)
; set character set B ($D5)
;
L037C:
        MOV     R0,#03Eh    ;load VCR shadow addr
        MOV     A,@R0       ;load VCR shadow
        ANL     A,#03Fh     ;set mode to 10 (external charset)
        ORL     A,#080h     ;(cont.)
        MOV     @R0,A       ;set VCR shadow
        MOV     VCR,A       ;set VCR
        CALL    L04E6       ;mask to low bit of command
        SWAP    A           ;...and move to bit 4
        RL      A           ;...no wait, bit 5
        MOV     R7,A        ;set A12-A13 to use on row pointers
L038A:
        MOV     R0,#020h    ;base of row pointer array
        MOV     R3,#019h    ;25 rows to set
L038E:
        MOV     A,@R0       ;load row pointer
        ANL     A,#09Fh     ;mask off A13-A14
        ORL     A,R7        ;merge in A13 as charset selector
        MOV     @R0,A       ;set row pointer
        INC     R0          ;next row
        DJNZ    R3,L038E    ;(cont.)
        MOV     HACC,A      ;
        CLR     A           ;
        MOV     SROW,A      ;update status row pointer
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; select XEP80 internal charset ($D6)
;
L039B:
        MOV     R0,#03Eh    ;load VCR shadow addr
        MOV     A,@R0       ;load VCR shadow
        ANL     A,#03Fh     ;set mode to 00 (internal charset)
        MOV     @R0,A       ;set VCR shadow
        MOV     VCR,A       ;set VCR
        MOV     R7,#040h    ;
        JMP     L038A       ;set A12-A13 to use on row pointers

;==========================================================================
; modify text to 50Hz ($D7)
;
; This modifies timing to:
;
;   12MHz / 735 = 16326Hz
;   27 rows * 12 lines + 3 lines = 327 lines / frame
;   16326Hz / 327 = 49.93Hz
;
L03A6:
        MOV     R3,#0B2h    ;12 line chars, 3 extra scanlines per frame
L03A8:
        MOV     R0,#004h    ;TCP 4 (char scan height - 1 : extra scans - 1)
L03AA:                      ;---set TCP index R0 to value R3
        MOV     A,R0        ;retrieve index to set
        MOV     TCP,A       ;set TCP index
        MOV     A,R3        ;retrieve value to set
        MOV     @TCP,A      ;set timing value
        RET

;==========================================================================
; cursor off ($D8)
;
L03AF:
        MOV     R3,#0B0h    ;invert cursor to turn it off
L03B1:
        MOV     R0,#00Dh    ;TCP 13 (cursor size register)
        JMP     L03AA       ;set TCP 13

;==========================================================================
; cursor on continuous ($D9)
; cursor blink ($DA)
;
L03B5:
        CALL    L04E6       ;get command bit 0
        RL      A           ;-> bit 1
        MOV     R3,A        ;save
        MOV     R0,#03Eh    ;load VCR shadow addr
        MOV     A,@R0       ;load VCR shadow
        ANL     A,#0FDh     ;mask off blink flag
        ORL     A,R3        ;merge in blink flag
        MOV     @R0,A       ;save VCR shadow
        MOV     VCR,A
        MOV     R3,#00Fh    ;full size cursor
        JMP     L03B1       ;set TCP 13

;==========================================================================
; Move cursor to start of logical line command ($DB)
;
L03C5:
        MOV     R7,#020h
        MOV     R3,#09Bh
L03C9:
        MOV     A,R1        ;get current Y
        XRL     A,R7        ;check if we're at row 0
        JZ      L03D7       ;exit if so
        DEC     R1          ;go up a row
        MOV     A,@R1       ;read current row pointer
        MOV     HACC,A      ;addr hi = current row
        MOV     A,R6        ;addr lo = right margin
        MOVL    R0,A        ;RA/R0 = address
        MOVX    A,@R0       ;read character from VRAM
        XRL     A,R3        ;check if EOL
        JNZ     L03C9       ;keep going if not
        INC     R1          ;go back down to desired row
L03D7:
        ;reset cursor
        MOV     A,@R1
        MOV     HACC,A
        MOV     A,R2
        MOV     CURS,A
        RET

;==========================================================================
; set scroll window to cursor X value ($DC)
;
L03DC:
        MOV     A,R2        ;load current cursor X
        SEL     RB1         ;-> intr ctx
        MOV     R7,A        ;set scroll pos
        SEL     RB0         ;-> main ctx
        RET

;==========================================================================
; set screen normal mode ($D2)
; set screen burst mode ($D3)
;
L03E1:
        MOV     R0,#03Fh    ;load burst var addr
        CALL    L04E6       ;mask to command bit 0
        MOV     @R0,A       ;set burst flag
        RET

;==========================================================================
; set printer output ($DD)
;
L03E7:
        MOV     R0,#03Fh    ;load flags addr
        MOV     A,#081h     ;set burst and printer flags
        MOV     @R0,A       ;set flags
        RET

;==========================================================================
; select white on black ($DE)
; select black on white ($DF)
;
L03ED:
        CALL    L04E6
        SWAP    A
        RR      A
        MOV     R3,A
        MOV     R0,#03Eh    ;load VCR shadow addr
        MOV     A,@R0       ;load VCR shadow
        ANL     A,#0F7h     ;turn off invert bit
        ORL     A,R3        ;merge invert bit
        MOV     @R0,A       ;set VCR shadow
        MOV     VCR,A       ;set VCR
        RET

;==========================================================================
; $90-9F command dispatch
L03FB:
        JMP     L029C

;==========================================================================
        DB      0FFh
        DB      0FFh
        
;==========================================================================
; $E0-FF command dispatch
;
L03FF:
        MOV     A,R3
        ANL     A,#01Fh
        JZ      L0479
        XCH     A,R4        ;last char -> A, subcmd -> R4
        DJNZ    R4,L0408
        
        ;$E1 - no-op (set R7 from last char)
        MOV     R7,A
L0408:
        DJNZ    R4,L040D
        
        ;$E2 - set cursor address
        MOV     HACC,A
        MOV     A,R7
        MOV     CURS,A
L040D:
        DJNZ    R4,L0410

        ;$E3 - write byte at cursor
        MOV     @CURS,A
L0410:
        DJNZ    R4,L0413
        
        ;$E4 - no-op (set R7 from last char)
        MOV     R7,A
L0413:
        DJNZ    R4,L0419

        ;$E5 - write internal location
        XCH     A,R7
        MOV     R0,A
        MOV     A,R7
        MOV     @R0,A
L0419:
        DJNZ    R4,L041C

        ;$E6 - no-op (set R7 from last char)
        MOV     R7,A
L041C:
        DJNZ    R4,L0421
        
        ;$E7 - set display home address
        MOV     HACC,A
        MOV     A,R7
        MOV     HOME,A
L0421:
        DJNZ    R4,L0424

        ;$E8 - write interrupt mask
        MOV     MASK,A
L0424:
        DJNZ    R4,L0427
        
        ;$E9 - write processor status word
        MOV     PSW,A
L0427:
        DJNZ    R4,L042A
        
        ;$EA - write printer port
        OUT     PORT
L042A:
        DJNZ    R4,L042D
        
        ;$EB - write timer counter
        MOV     T,A
L042D:
        DJNZ    R4,L0430
        
        ;$EC - write system control register
        MOV     SCR,A
L0430:
        DJNZ    R4,L0433
        
        ;$ED - write video control register
        MOV     VCR,A
L0433:
        DJNZ    R4,L0436
        
        ;$EE - no-op (set R7 from last char)
        MOV     R7,A
L0436:
        DJNZ    R4,L043B
        
        ;$EF - set display begin address
        MOV     HACC,A
        MOV     A,R7
        MOV     BEGD,A
L043B:
        DJNZ    R4,L043E
        
        ;$F0 - no-op (set R7 from last char)
        MOV     R7,A
L043E:
        DJNZ    R4,L0443
        
        ;$F1 - set display end address
        MOV     HACC,A
        MOV     A,R7
        MOV     ENDD,A
L0443:
        DJNZ    R4,L0446
        
        ;$F2 - no-op (set R7 from last char)
        MOV     R7,A
L0446:
        DJNZ    R4,L044B

        ;$F3 - set status row register
        MOV     HACC,A
        MOV     A,R7
        MOV     SROW,A
L044B:
        DJNZ    R4,L044E

        ;$F4 - set attribute latch 0
        MOV     AL0,A
L044E:
        DJNZ    R4,L0451

        ;$F5 - set attribute latch 1
        MOV     AL1,A
L0451:
        DJNZ    R4,L0454

        ;$F6 - set timing control pointer
        MOV     TCP,A
L0454:
        DJNZ    R4,L0457
        
        ;$F7 - set timing control register
        MOV     @TCP,A
L0457:
        DJNZ    R4,L045A
        
        ;$F8 - set vertical interrupt register
        MOV     VINT,A
L045A:
        DJNZ    R4,L045D
        
        ;$F9 - no-op (set R7 from last char)
        MOV     R7,A
L045D:
        DJNZ    R4,L0462
        
        ;$FA - set baud rate
        MOV     PSR,A
        MOV     A,R7
        MOV     BAUD,A
L0462:
        DJNZ    R4,L0465
        
        ;$FB - set UART control register
        MOV     UCR,A
L0465:
        DJNZ    R4,L0468
        
        ;$FC - set UART multiplex register
        MOV     UMX,A
L0468:
        DJNZ    R4,L046B
        
        ;$FD - set UART transmit register
        OUT     XMTR
L046B:
        DJNZ    R4,L046F
        
        ;$FE - no-op
        JMP     L046F

L046F:
        DJNZ    R4,L0479

        ;$FF - strobe printer
        DIS     II
        SEL     MB2
        JMP     L0475

L0475:
        SEL     MB0
        JMP     L0478

L0478:
        EN      II
        
L0479:
        ;$E0
        RET

;==========================================================================
; insert char ($FF), continued
;
L047A:
        MOV     A,R1            ;get current Y
        MOV     R0,#03Dh        ;
        MOV     @R0,A           ;
        MOV     R7,#020h
        MOV     A,R2
L0481:
        CPL     A               ;acc = ~x
        INC     A               ;acc = -x
        ADD     A,R6            ;acc = rmargin - x
        JZ      L04AF           ;skip scroll on this line if we're at the end
        MOV     R3,A            ;
        MOV     A,R7
        MOV     R4,A
        MOV     A,@R0           ;get current Y
        MOV     R0,A
        MOV     A,@R0           ;load row address
        MOV     HACC,A          ;-> address hi
        MOV     A,R6            ;load right margin -> address lo
        MOVL    R0,A            ;load address
        MOVX    A,@R0           ;read char at right margin
        MOV     R7,A            ;stash it
L0491:
        DECL    R0              ;go left one
        MOVX    A,@R0           ;read char
        INCL    R0              ;go back
        MOVX    @R0,A           ;deposit it one char right
        DECL    R0              ;...and left one again
        DJNZ    R3,L0491        ;loop back until everything shifted to cursor
        MOV     A,R4            ;retrieve pushed char
        MOVX    @R0,A           ;drop it in the newly vacated space
        MOVL    A,R0            ;address -> hacc/acc
        MOV     A,R6            ;move over to right margin
        MOVL    R0,A            ;hacc/acc -> address
        MOVX    A,@R0           ;read char at right margin
L049E:
        MOV     R3,A            ;stash it
        MOV     A,R7            ;retrieve char pushed off of current line
        XRL     A,#09Bh         ;check if it was an EOL
        JZ      L04B7           ;if so, jump out... we may need to insert a line
        MOV     R0,#03Dh        ;load current row var addr
        MOV     A,@R0           ;read it
        XRL     A,#037h         ;are we at row 23?
        JZ      L04D3           ;if so, exit
        INC     @R0             ;nope, so let's go to the next row
        MOV     A,R5            ;use left margin as insertion point
        JMP     L0481           ;...and go back to scroll another line

L04AF:
        MOV     A,R7            ;load insertion char
        MOV     R4,A            ;
        MOV     A,@CURS         ;load char under cursor
        MOV     R7,A            ;
        MOV     A,R4            ;
        MOV     @CURS,A         ;
        JMP     L049E

L04B7:                          ;--- extend logical line check
        MOV     A,R3            ;load char now at right margin
        XRL     A,#09Bh         ;check if it is an EOL
        JZ      L04D3           ;if so, the line is short, no need to insert
        MOV     R0,#03Dh        ;load current row var addr
        MOV     A,@R0           ;read it
        XRL     A,#037h         ;are we at row 23?
        JNZ     L04D7           ;if not, go scroll everything else down
        MOV     A,R1            ;load current Y
        XRL     A,#020h         ;check if on row 0
        JZ      L04D3           ;if so, exit... the whole screen is one logical line (!)
        MOV     A,R1            ;load current Y
        DEC     A               ;go up one row
        MOV     R3,A
        MOV     A,R2
        MOV     R4,A
        CALL    L0652           ;scroll screen up to make room below
        MOV     A,R3
        MOV     R1,A
L04D1:
        MOV     A,R4
        MOV     R2,A
L04D3:
        MOV     A,#0FFh
        MOV     R4,A
        RET

L04D7:                          ;--- scroll down routine
        MOV     A,R1
        MOV     R7,A
        MOV     A,@R0
        INC     A
        MOV     R1,A
        MOV     A,R2
        MOV     R4,A
        CALL    L053D           ;scroll
        CALL    L06B6           ;blank line
        MOV     A,R7
        MOV     R1,A
        JMP     L04D1

L04E6:
        MOV     A,R3
        ANL     A,#001h
        RET

;==========================================================================
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        
;==========================================================================
L0500:
        ADD     A,#003h
        JMPP    @A

        DB      023h    ;$1C - up
        DB      0C3h    ;$3C
        DB      0C3h    ;$5C
        DB      0C3h    ;$7C
        DB      0C1h    ;$9C - delete line
        DB      0C3h    ;$BC
        DB      0C3h    ;$DC
        DB      0C3h    ;$FC
        DB      02Dh    ;$1D - down
        DB      0C3h    ;$3D
        DB      0C3h    ;$5D
        DB      06Fh    ;$7D - clear
        DB      037h    ;$9D - insert line
        DB      0C3h    ;$BD
        DB      0C3h    ;$DD
        DB      083h    ;$FD - bell (no-op)
        DB      052h    ;$1E - left
        DB      0C3h    ;$3E
        DB      0C3h    ;$5E
        DB      07Ah    ;$7E - backspace
        DB      09Bh    ;$9E - clear tab
        DB      0C3h    ;$BE
        DB      0C3h    ;$DE
        DB      0A5h    ;$FE - delete char
        DB      05Ch    ;$1F - right
        DB      0C3h    ;$3F
        DB      0C3h    ;$5F
        DB      0A7h    ;$7F - tab
        DB      09Bh    ;$9F - set tab
        DB      0C3h    ;$BF
        DB      0C3h    ;$DF
        DB      0BFh    ;$FF - insert char

;==========================================================================
; move up ($1C)
;
L0523:
        MOV     A,R1        ;get current Y
        XRL     A,#020h     ;check if we are at row 0
        JNZ     L052A       ;skip if not
        MOV     R1,#038h    ;move to row 24
L052A:
        DEC     R1          ;move up one row
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; move down ($1D)
;
L052D:
        MOV     A,R1        ;get current Y
        XRL     A,#037h     ;check if we are at row 23
        JNZ     L0534       ;skip if not
        MOV     R1,#01Fh    ;move to row -1
L0534:
        INC     R1          ;move down one row
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; insert and clear line ($7D)
;
L0537:
        CALL    L053D       ;rotate up
        CALL    L06B6       ;clear line with EOL
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; insert line routine
;
L053D:
        MOV     A,R1        ;get cursor Y
        CPL     A           ;-Y-1
        ADD     A,#038h     ;$37-Y
        JZ      L0551       ;exit if already on last row
        MOV     R3,A
        MOV     R0,#037h    ;select row 23
        MOV     A,@R0       ;get row 23 pointer
        MOV     HACC,A      ;stash row 23 pointer
L0548:
        DEC     R0
        MOV     A,@R0       ;get next row pointer
        INC     R0
        MOV     @R0,A       ;write to previous row pointer
        DEC     R0          ;one row down
        DJNZ    R3,L0548    ;loop back until all row pointers moved
        
        MOV     A,HACC      ;retrieve stashed row
        MOV     @R0,A       ;store stashed row as new row
L0551:
        RET

;==========================================================================
; move left ($1E)
;
L0552:
        MOV     A,R2        ;get current X
        XRL     A,R5        ;check if at left margin
        JNZ     L0559       ;skip if not
        MOV     A,R6        ;load right margin
        INC     A           ;move beyond one (to be backed off below)
        MOV     R2,A        ;set current X to rmargin+1
L0559:
        DEC     R2          ;move left one
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; move right ($1F)
;
L055C:
        MOV     A,R2        ;get current X
        XRL     A,R6        ;check if at right margin
        JZ      L056B       ;jump if so
        MOV     A,@CURS     ;get character at cursor
        XRL     A,#09Bh     ;check if EOL
        JNZ     L0568       ;jump if not
        MOV     A,#020h     ;load a space
        MOV     @CURS,A     ;replace the EOL with a space
L0568:
        INC     R2          ;move right one spot
        JMP     L05E2       ;recompute cursor address and exit

L056B:
        MOV     A,R5        ;load left margin
        MOV     R2,A        ;set X to left margin
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; clear ($7D)
;
L056F:
        MOV     R3,#018h
        MOV     R1,#038h
L0573:
        DEC     R1
        CALL    L06B6       ;clear line to EOL
        DJNZ    R3,L0573
        JMP     L05E2       ;recompute cursor address and exit

;==========================================================================
; backspace ($7A)
;
L057A:
        MOV     A,R2        ;get current X
        XRL     A,R5        ;check if at left margin
        JZ      L0584       ;jump if we need to back into the prev line
        DEC     R2          ;move one left one
        DEC     CURS        ;decrement cursor address to match
L0580:
        MOV     A,#020h     ;load a space
        MOV     @CURS,A     ;blank the spot we landed on
L0583:                      ;bell enters here (NOP)
        RET

L0584:
        MOV     A,R1        ;get current Y
        MOV     R0,A        ;stash it
        XRL     A,#020h     ;check if we're on row 0
        JZ      L0583       ;just exit if so... nowhere to go
        DEC     R0          ;go to previous line
        MOV     A,@R0       ;load row address
        MOV     HACC,A      ;-> address hi
        MOV     A,R6        ;right margin -> address lo
        MOVL    R0,A        ;right margin of prev line -> address
        MOVX    A,@R0       ;read rmargin char
        XRL     A,#09Bh     ;check if it is an EOL
        JZ      L0583       ;exit if so... can't back into prev logical line
        DEC     R1          ;go up a line
        MOV     A,R6        ;load right margin
        MOV     R2,A        ;-> new X
        CALL    L05E2       ;recompute cursor address
        JMP     L0580       ;blank current spot and exit

;==========================================================================
; clear tab ($9E)
; set tab ($9F)
;
L059B:
        MOV     A,#019h     ;load tab table addr
        MOV     HACC,A      ;-> address high
        MOV     A,R2        ;load X pos -> address lo
        MOVL    R0,A        ;load address
        MOV     A,R3        ;load control code
        ANL     A,#001h     ;mask to bool
        MOVX    @R0,A       ;modify tab table
        RET

;==========================================================================
; delete char ($FE)
;
L05A5:
        JMP     L0760

;==========================================================================
; tab ($7F)
;
L05A7:
        MOV     A,@CURS     ;load char under cursor
        MOV     HACC,A      ;stash it
        XRL     A,#09Bh     ;check if it's an EOL
        JNZ     L05B0       ;skip if not
        MOV     A,#020h     ;load a blank
        MOV     @CURS,A     ;overwrite the EOL with it
L05B0:
        CALL    L05C7       ;advance one character
        MOV     A,#019h     ;load tab table addr
        MOV     HACC,A      ;-> address hi
        MOV     A,R2        ;load cursor X
        XRL     A,R6        ;check if at right margin
        JZ      L0583       ;exit if so
        MOV     A,R2        ;X -> address lo
        MOVL    R0,A        ;load address
        MOVX    A,@R0       ;read tab table entry
        JZ      L05A7       ;keep going if not yet at a tab
        RET

;==========================================================================
; insert char ($FF)
;
L05BF:
        JMP     L047A

;==========================================================================
; delete line ($9C)
;
L05C1:
        JMP     L07B4

;==========================================================================
; write character
;
L05C3:
        MOV     A,@CURS     ;read char under cursor
        MOV     HACC,A      ;stash it
        MOV     A,R3        ;load char to write
        MOV     @CURS,A     ;write it at cursor
L05C7:
        MOV     R0,#03Ch
        CLR     A
        MOV     @R0,A
L05CB:
        MOV     A,R2        ;get cursor X
        XRL     A,R6        ;check if equal to right margin
        JNZ     L0568       ;jump if not
        MOV     A,R1        ;get cursor Y
        XRL     A,#037h     ;check if at row 23
        JZ      L05E0       ;jump if so
        XRL     A,#00Fh     ;check if at row 24 (status)
        JZ      L056B       ;jump if so
        INC     R1          ;advance to next row
        MOV     A,HACC      ;retrieve char we overwrote
        XRL     A,#09Bh     ;check if EOL
        JZ      L0537       ;jump to insert line if so
        JMP     L056B       ;recompute cursor address and exit

L05E0:
        CALL    L0652       ;scroll screen
L05E2:
        MOV     A,@R1       ;get current row pointer
        MOV     HACC,A      ;set cursor addr high byte
        MOV     A,R2        ;get current column
        MOV     CURS,A      ;set cursor address
        RET

;==========================================================================
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh

;==========================================================================
; $C1-CF dispatch
;
; On return from these routines, A is sent back to the host.
;
L0600:
        ADD     A,#003h
        JMPP    @A

        DB      01Eh        ;$C0 - get char (not possible)
        DB      013h        ;$C1 - request horizontal cursor
        DB      015h        ;$C2 - master reset
        DB      031h        ;$C3 - printer port status
        DB      036h        ;$C4 - fill RAM with previous char
        DB      03Dh        ;$C5 - fill RAM with space
        DB      041h        ;$C6 - fill RAM with EOL
        DB      01Fh        ;$C7 - "reserved" - read char at cursor
        DB      021h        ;$C8 - "reserved" - read interrupt status
        DB      023h        ;$C9 - "reserved" - read processor word
        DB      025h        ;$CA - "reserved" - read printer port
        DB      027h        ;$CB - "reserved" - read timer
        DB      029h        ;$CC - "reserved" - read horizontal light pen
        DB      02Bh        ;$CD - "reserved" - read vertical light pen
        DB      02Dh        ;$CE - "reserved" - read UART status
        DB      02Fh        ;$CF - "reserved" - read UART buffer

;==========================================================================
; request horizontal cursor ($C1)
;
L0613:
        MOV     A,R2        ;load X pos
        RET

;==========================================================================
; master reset ($C2)
;
L0615:
        DIS     II
        DIS     XI
        CALL    L001F
        CALL    L0056
        EN      II
L061C:
        MOV     A,#001h
L061E:
        RET

;==========================================================================
; get char without advancing ($C7) - undocumented
;
L061F:
        MOV     A,@CURS
        RET

;==========================================================================
; get interrupt status ($C8) - undocumented
;
L0621:
        MOV     A,INTR
        RET

;==========================================================================
; get processor status word ($C9) - undocumented
;
L0623:
        MOV     A,PSW
        RET

;==========================================================================
; get printer port data ($CA) - undocumented
;
L0625:
        IN      PORT
        RET

;==========================================================================
; get timer counter ($CB) - undocumented
;
L0627:
        MOV     A,T
        RET

;==========================================================================
; get horizontal pen position ($CC) - undocumented
;
L0629:
        MOV     A,HPEN
        RET

;==========================================================================
; get vertical pen position ($CD) - undocumented
;
L062B:
        MOV     A,VPEN
        RET

;==========================================================================
; get UART status ($CE) - undocumented
;
L062D:
        MOV     A,STAT
        RET

;==========================================================================
; get UART input buffer data ($CF) - undocumented
;
L062F:
        IN      RCVR
        RET

;==========================================================================
; printer port status ($C3)
;
L0631:
        CLR     A
        JXI     L0635
        INC     A
L0635:
        RET

;==========================================================================
; fill RAM with previous char ($C4)
;
L0636:
        MOV     A,R4        ;load previous char
        MOV     R3,A        ;-> fill char
        CALL    L02D7       ;reverse bit order
        MOV     R3,A        ;set as fill char
        JMP     L0643       ;do fill

L063D:
        MOV     R3,#020h    ;set space as fill char
        JMP     L0643       ;do fill

L0641:
        MOV     R3,#09Bh    ;set EOL as fill char
L0643:
        CLR     A           ;zero -> address lo
        MOV     HACC,A      ;-> address hi
        MOVL    R0,A        ;set address zero
        MOV     R7,A        ;lo count = 0
        MOV     A,R3        ;fill byte -> acc
        MOV     R3,#020h    ;hi count = $20 ($2000 to clear)
L064A:
        MOVX    @R0,A       ;fill a byte
        INCL    R0          ;next addr
        DJNZ    R7,L064A    ;loop back to do page
        DJNZ    R3,L064A    ;loop back until $0000-1FFF filled
        JMP     L061C       ;return $01 to host

;==========================================================================
; scroll routine
;
L0652:
        MOV     R0,#020h
        MOV     R1,#021h
        
        ;stash first row pointer
        MOV     A,@R0
        MOV     HACC,A

        ;shift 23 row pointers up one row
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        INC     R1
        INC     R0
        MOV     A,@R1
        MOV     @R0,A
        
        ;move the stashed first row to the last
        MOV     A,HACC
        MOV     @R1,A
    
        MOV     R1,#037h
L06B6:
        MOV     A,@R1
        MOV     HACC,A
        SEL     RB1         ;switch to int bank
        MOV     A,R7        ;get X scroll value
        SEL     RB0         ;switch to main bank
        MOVL    R0,A
        
        ;clear 80 bytes with EOL
        MOV     A,#09Bh
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        INCL    R0
        MOVX    @R0,A
        
        ;set cursor X to left margin
        MOV     A,R5
        MOV     R2,A
        RET

;==========================================================================
; delete char ($FE), continued
;
L0760:
        MOV     A,R1
        MOV     R3,A
L0762:                      ;--- determine bottom of logical line
        MOV     A,R1        ;load current Y
        XRL     A,#037h     ;check if we're on row 23
        JZ      L0773       ;if so, no need to check further rows
        MOV     A,@R1       ;read row pointer
        MOV     HACC,A      ;-> address hi
        MOV     A,R6        ;right margin -> address lo
        MOVL    R0,A        ;load address
        MOVX    A,@R0       ;read character
        XRL     A,#09Bh     ;check if it is an EOL
        JZ      L0773       ;yes, we can begin deleting chars
        INC     R1          ;go down one line
        JMP     L0762       ;and keep checking

L0773:
        MOV     A,R1        ;get row pointer
        INC     A           ;next row
        MOV     R0,#03Dh    ;temp addr
        MOV     @R0,A       ;stash end of logical line
        MOV     A,R3        ;get original Y
        MOV     R1,A        
        MOV     R3,#09Bh    ;set EOL to come in at end of last line
L077C:                      ;--- line shifting loop
        MOV     R0,#03Dh    ;
        MOV     A,@R0       ;
        DEC     A           ;
        MOV     @R0,A       ;
        MOV     R0,A
        XRL     A,R1        ;check if we're back to the current row
        JZ      L0795       ;if so, exit
        MOV     A,R5        ;load left margin
        CPL     A           ;acc = ~lmargin
        INC     A           ;acc = -lmargin
        ADD     A,R6        ;acc = rmargin - lmargin
        JZ      L07A2       ;if the screen is one char wide, just stop now
        MOV     R7,A
        MOV     A,@R0       ;load row pointer
        MOV     HACC,A      ;-> address hi
        MOV     A,R5        ;set address lo to right margin
        CALL    L07A7       ;delete and shift chars
        MOV     A,R4        ;get char that was deleted
        MOV     R3,A        ;set that to come in at end of prev line
        JMP     L077C       ;loop back to shift the previous line

L0795:                      ;---back at current row
        MOV     A,R2        ;load current X
        CPL     A           ;~x
        INC     A           ;-x
        ADD     A,R6        ;acc = rmargin - x
        JZ      L07A2       ;if we're at the right margin, shift in and exit
        MOV     R7,A        ;set chars to shift
        MOV     A,CURS      ;use cursor as the position to delete
        CALL    L07A7       ;delete and shift
        MOV     R4,#0FEh    ;set last char to $FE (delete char)
        RET

L07A2:
        MOV     A,R3        ;load char to shift in
        MOV     @CURS,A     ;write it at cursor
        MOV     R4,#0FEh    ;set last char to $FE (delete char)
        RET

;==========================================================================
; delete and shift characters
;
; input:
;   HACC/A = address of char to delete
;   R3 = character to insert at end
;   R7 = number of characters to shift
;
; output:
;   R4 = deleted character
;
L07A7:
        MOVL    R0,A        ;load address
        MOVX    A,@R0       ;read char to be deleted
        MOV     R4,A        ;stash it
L07AA:
        INCL    R0          ;go right one
        MOVX    A,@R0       ;fetch char from one right
        DECL    R0          ;left one
        MOVX    @R0,A       ;deposit it one left
        INCL    R0          ;and go back right
        DJNZ    R7,L07AA    ;loop back until R7 chars shifted left
        MOV     A,R3        ;get the char to shift in
        MOVX    @R0,A       ;stick it at the end
        RET

;==========================================================================
; delete line ($9C), continued
;
L07B4:
        MOV     A,R1        ;load current Y
        XRL     A,#037h     ;check if on row 23
        JZ      L07DF       ;if so, just blank and exit
L07B9:
        MOV     A,CURS      ;read cursor address
        MOV     A,R6        ;change address lo to right margin
        MOVL    R0,A        ;load address
        MOVX    A,@R0       ;read char
        MOV     R7,A        ;stash it
        MOV     A,R1        ;load current Y
        MOV     R0,A        ;-> row pointer
        CPL     A           ;~y
        ADD     A,#038h     ;acc = 37-y
        MOV     R3,A        ;
        MOV     A,@R0       ;load current row pointer
        MOV     HACC,A      ;stash it
L07C6:                      ;--- rotate row pointers up
        INC     R0          ;next row
        MOV     A,@R0       ;read row pointer
        DEC     R0          ;back to prev row
        MOV     @R0,A       ;relocate row one up
        INC     R0          ;next row
        DJNZ    R3,L07C6    ;loop back until all rows below moved
        MOV     A,HACC      ;retrieve row pointer we stashed
        MOV     @R0,A       ;drop it in as row 23
        MOV     A,R1        ;load current Y
        MOV     R3,A        ;stash it (blank routine overwrites it)
        MOV     R1,#037h    ;select row 23
        CALL    L06B6       ;blank row 23
        MOV     A,R3        ;retrieve current Y
        MOV     R1,A        ;put it back
        CALL    L05E2       ;recompute cursor address
        MOV     A,R7        ;retrieve char from right margin of deleted line
        XRL     A,#09Bh     ;check if it was an EOL
        JNZ     L07B9       ;if not, delete another line
        RET

L07DF:
        CALL    L06B6       ;blank the current line
        JMP     L05E2       ;recompute cursor address and exit

        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
        DB      0FFh
